// Copyright 2016 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef DRACO_ATTRIBUTES_POINT_ATTRIBUTE_H_
#define DRACO_ATTRIBUTES_POINT_ATTRIBUTE_H_

#include <memory>

#include "draco/draco_features.h"

#include "draco/attributes/attribute_transform_data.h"
#include "draco/attributes/geometry_attribute.h"
#include "draco/core/draco_index_type_vector.h"
#include "draco/core/hash_utils.h"
#include "draco/core/macros.h"

namespace draco {

// Class for storing point specific data about each attribute. In general,
// multiple points stored in a point cloud can share the same attribute value
// and this class provides the necessary mapping between point ids and attribute
// value ids.
class PointAttribute : public GeometryAttribute {
  public:
    PointAttribute();
    explicit PointAttribute(const GeometryAttribute &att);

    // Make sure the move constructor is defined (needed for better performance
    // when new attributes are added to PointCloud).
    PointAttribute(PointAttribute &&attribute) = default;
    PointAttribute &operator=(PointAttribute &&attribute) = default;

    // Copies attribute data from the provided |src_att| attribute.
    void CopyFrom(const PointAttribute &src_att);

    // Prepares the attribute storage for the specified number of entries.
    bool Reset(size_t num_attribute_values);

    size_t size() const {
        return num_unique_entries_;
    }
    AttributeValueIndex mapped_index(PointIndex point_index) const {
        if (identity_mapping_)
            return AttributeValueIndex(point_index.value());
        return indices_map_[point_index];
    }
    DataBuffer *buffer() const {
        return attribute_buffer_.get();
    }
    bool is_mapping_identity() const {
        return identity_mapping_;
    }
    size_t indices_map_size() const {
        if (is_mapping_identity())
            return 0;
        return indices_map_.size();
    }

    const uint8_t *GetAddressOfMappedIndex(PointIndex point_index) const {
        return GetAddress(mapped_index(point_index));
    }

    // Sets the new number of unique attribute entries for the attribute.
    void Resize(size_t new_num_unique_entries) {
        num_unique_entries_ = new_num_unique_entries;
    }

    // Functions for setting the type of mapping between point indices and
    // attribute entry ids.
    // This function sets the mapping to implicit, where point indices are equal
    // to attribute entry indices.
    void SetIdentityMapping() {
        identity_mapping_ = true;
        indices_map_.clear();
    }
    // This function sets the mapping to be explicitly using the indices_map_
    // array that needs to be initialized by the caller.
    void SetExplicitMapping(size_t num_points) {
        identity_mapping_ = false;
        indices_map_.resize(num_points, kInvalidAttributeValueIndex);
    }

    // Set an explicit map entry for a specific point index.
    void SetPointMapEntry(PointIndex point_index,
                          AttributeValueIndex entry_index) {
        DRACO_DCHECK(!identity_mapping_);
        indices_map_[point_index] = entry_index;
    }

    // Sets a value of an attribute entry. The input value must be allocated to
    // cover all components of a single attribute entry.
    void SetAttributeValue(AttributeValueIndex entry_index, const void *value) {
        const int64_t byte_pos = entry_index.value() * byte_stride();
        buffer()->Write(byte_pos, value, byte_stride());
    }

    // Same as GeometryAttribute::GetValue(), but using point id as the input.
    // Mapping to attribute value index is performed automatically.
    void GetMappedValue(PointIndex point_index, void *out_data) const {
        return GetValue(mapped_index(point_index), out_data);
    }

#ifdef DRACO_ATTRIBUTE_DEDUPLICATION_SUPPORTED
    // Deduplicate |in_att| values into |this| attribute. |in_att| can be equal
    // to |this|.
    // Returns -1 if the deduplication failed.
    AttributeValueIndex::ValueType DeduplicateValues(
        const GeometryAttribute &in_att);

    // Same as above but the values read from |in_att| are sampled with the
    // provided offset |in_att_offset|.
    AttributeValueIndex::ValueType DeduplicateValues(
        const GeometryAttribute &in_att, AttributeValueIndex in_att_offset);
#endif

    // Set attribute transform data for the attribute. The data is used to store
    // the type and parameters of the transform that is applied on the attribute
    // data (optional).
    void SetAttributeTransformData(
        std::unique_ptr<AttributeTransformData> transform_data) {
        attribute_transform_data_ = std::move(transform_data);
    }
    const AttributeTransformData *GetAttributeTransformData() const {
        return attribute_transform_data_.get();
    }

  private:
#ifdef DRACO_ATTRIBUTE_DEDUPLICATION_SUPPORTED
    template <typename T>
    AttributeValueIndex::ValueType DeduplicateTypedValues(
        const GeometryAttribute &in_att, AttributeValueIndex in_att_offset);
    template <typename T, int COMPONENTS_COUNT>
    AttributeValueIndex::ValueType DeduplicateFormattedValues(
        const GeometryAttribute &in_att, AttributeValueIndex in_att_offset);
#endif

    // Data storage for attribute values. GeometryAttribute itself doesn't own its
    // buffer so we need to allocate it here.
    std::unique_ptr<DataBuffer> attribute_buffer_;

    // Mapping between point ids and attribute value ids.
    IndexTypeVector<PointIndex, AttributeValueIndex> indices_map_;
    AttributeValueIndex::ValueType num_unique_entries_;
    // Flag when the mapping between point ids and attribute values is identity.
    bool identity_mapping_;

    // If an attribute contains transformed data (e.g. quantized), we can specify
    // the attribute transform here and use it to transform the attribute back to
    // its original format.
    std::unique_ptr<AttributeTransformData> attribute_transform_data_;

    friend struct PointAttributeHasher;
};

// Hash functor for the PointAttribute class.
struct PointAttributeHasher {
    size_t operator()(const PointAttribute &attribute) const {
        GeometryAttributeHasher base_hasher;
        size_t hash = base_hasher(attribute);
        hash = HashCombine(attribute.identity_mapping_, hash);
        hash = HashCombine(attribute.num_unique_entries_, hash);
        hash = HashCombine(attribute.indices_map_.size(), hash);
        if (attribute.indices_map_.size() > 0) {
            const uint64_t indices_hash = FingerprintString(
                                              reinterpret_cast<const char *>(attribute.indices_map_.data()),
                                              attribute.indices_map_.size());
            hash = HashCombine(indices_hash, hash);
        }
        if (attribute.attribute_buffer_ != nullptr) {
            const uint64_t buffer_hash = FingerprintString(
                                             reinterpret_cast<const char *>(attribute.attribute_buffer_->data()),
                                             attribute.attribute_buffer_->data_size());
            hash = HashCombine(buffer_hash, hash);
        }
        return hash;
    }
};

}  // namespace draco

#endif  // DRACO_ATTRIBUTES_POINT_ATTRIBUTE_H_
